package com.springboot.autoconfigure.oss.config.auto;

import cn.hutool.core.util.ReflectUtil;
import com.springboot.autoconfigure.oss.annontion.RequiredFiled;
import com.springboot.autoconfigure.oss.config.*;
import com.springboot.autoconfigure.oss.service.AbstractLocalFilePathHelper;
import com.springboot.autoconfigure.oss.service.DefaultLocalFilePathHelper;
import com.springboot.autoconfigure.oss.service.FileNameService;
import com.springboot.autoconfigure.oss.service.OssApiService;
import com.springboot.autoconfigure.oss.service.impl.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.beans.factory.support.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.SpringFactoriesLoader;

import java.beans.Introspector;
import java.lang.reflect.Field;
import java.util.*;

/**
 * 用SPI的思想实现多个OSS服务器厂商;
 */
@Slf4j
@Configuration
@ComponentScan(basePackages = {"com.springboot.autoconfigure.oss","cn.hutool.extra.spring"})
@ConditionalOnProperty(
        value = {"oss.enabled"},
        havingValue = "true",
        matchIfMissing = true
)
@EnableConfigurationProperties({AliyunOSSPropertis.class})
@Order(Integer.MAX_VALUE)
public class OSSAutoConfiguration implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware {
    private ApplicationContext applicationContext;
    private ApplicationContext parentApplicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        this.parentApplicationContext = applicationContext.getParent();
    }

    /**
     * 手动加载配置文件——用于本地测试
     * TODO 测试手动开启
     * @return
     */

//    @ConditionalOnProperty(value = "osstest",matchIfMissing = false)
    @Bean
    public  PropertySourcesPlaceholderConfigurer loadOssYamlproperties() {
        PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
        YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
        yaml.setResources((Resource) new ClassPathResource("osstest.yml"));
        propertySourcesPlaceholderConfigurer.setProperties(yaml.getObject());
        return propertySourcesPlaceholderConfigurer;
    }


    /**
     * 默认文件名生成策略
     */
    @Bean
    @ConditionalOnMissingBean(value = {FileNameService.class})
    public FileNameService fileNameService(){
        DefaultFileNameServiceImpl defaultFileNameService = new DefaultFileNameServiceImpl();
        return defaultFileNameService;
    }



    /**
     * 本地文件上传获取url路径
     */
    @Bean
    @ConditionalOnMissingBean(value = {AbstractLocalFilePathHelper.class})
    public AbstractLocalFilePathHelper abstractLocalFilePathHelper(FileNameService fileNameService){
        AbstractLocalFilePathHelper defaultLocalFilePathHelper = new DefaultLocalFilePathHelper(fileNameService);
        return defaultLocalFilePathHelper;
    }

    /**
     * 必传字段为空不加入beanFactory
     * @param abstractOSSPropertis
     * @return
     */
    public boolean checkOSSPropertisField(AbstractOSSPropertis abstractOSSPropertis){
        Class<? extends AbstractOSSPropertis> clazz = abstractOSSPropertis.getClass();
        Field[] fields = ReflectUtil.getFields(clazz);
        for (Field field : fields) {
            if (field.getAnnotationsByType(RequiredFiled.class)!=null){
                RequiredFiled[] annotationsByType = field.getAnnotationsByType(RequiredFiled.class);
                Optional<RequiredFiled> first = Arrays.stream(annotationsByType).findFirst();
                if (first.isPresent() && first.get().value()){
                    Object fieldValue = ReflectUtil.getFieldValue(abstractOSSPropertis, field);
                    if (null == fieldValue){
                        log.error("OSS spring boot start check OSS Class Name:"+abstractOSSPropertis.getClass().getName()+"Filed "+field.getName()+" is null");
                        return false;
                    }
                }
            }
        }
        return true;
    }


    private BeanDefinitionRegistry registry;
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        this.registry = registry;
        // 1.方式1 根据SPI 配置文件的方式导入  不建议使用
        List<OssApiService> ossApiServices = SpringFactoriesLoader.loadFactories(OssApiService.class, this.getClass().getClassLoader());
        if (!ossApiServices.isEmpty()){
            registryOSSimpl(registry, ossApiServices);
            return;
        }

    }

    private void registryOSSimpl(BeanDefinitionRegistry registry, List<OssApiService> ossApiServices) {
        for (OssApiService ossApiService : ossApiServices) {
            sigleOssApiServiceClazzRegistry(registry, ossApiService.getClass());
        }
    }

     public static void sigleOssApiServiceClazzRegistry(BeanDefinitionRegistry registry, Class<? extends OssApiService> ossApiServiceClazz) {
        GenericBeanDefinition rootBeanDefinition = new GenericBeanDefinition();
        rootBeanDefinition.setBeanClass(ossApiServiceClazz);
        rootBeanDefinition.setScope(OssApiService.SCOPE_OSSIMPL);
        registry.registerBeanDefinition(Introspector.decapitalize(ossApiServiceClazz.getSimpleName()),rootBeanDefinition);
    }

    public static void sigleOssApiServiceClazzRegistryDependsOn(BeanDefinitionRegistry registry, Class<? extends OssApiService> ossApiServiceClazz) {
        GenericBeanDefinition rootBeanDefinition = new GenericBeanDefinition();
        rootBeanDefinition.setBeanClass(ossApiServiceClazz);
        rootBeanDefinition.setScope(OssApiService.SCOPE_OSSIMPL);
        // 获取泛型接口的类型
        Class<?>[] classes = GenericTypeResolver.resolveTypeArguments(ossApiServiceClazz, OssApiService.class);
        rootBeanDefinition.setDependsOn(Introspector.decapitalize(classes[0].getSimpleName()),"ossConfigPropertis");
        registry.registerBeanDefinition(Introspector.decapitalize(ossApiServiceClazz.getSimpleName()),rootBeanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.registerScope(OssApiService.SCOPE_OSSIMPL,new OssServiceImplScope());
        Map<String, AbstractOSSPropertis> beansOfType = beanFactory.getBeansOfType(AbstractOSSPropertis.class);
        if (null ==beansOfType || beansOfType.keySet().isEmpty())return;
        Set<Map.Entry<String, AbstractOSSPropertis>> entries = beansOfType.entrySet();
        for (Map.Entry<String, AbstractOSSPropertis> entry : entries) {
            // TODO 判断是否有必填字段 没有获取到值
//            AbstractOSSPropertis bean = null;
//            try {
//                AbstractApplicationContext applicationContext = (AbstractApplicationContext) this.applicationContext;
//                // # BUG 没有获取到属性的值 ConfigurationPropertiesBindingPostProcessor 对象没有实例化 bean
//                beanFactory.addBeanPostProcessor(new ConfigurationPropertiesBindingPostProcessor());
//                beanFactory.getBean("org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor");
//                bean =  this.applicationContext.getBean(entry.getKey(), AbstractOSSPropertis.class);
//            } catch (Exception e) {
//                e.printStackTrace();
//                continue;
//            }
//            // 先判断@RequiredFiled注解 为空的字段
//            if (null == bean || !checkOSSPropertisField(bean))continue;
            if (entry.getKey().equals("oss.aliyun-com.springboot.autoconfigure.oss.config.AliyunOSSPropertis"))continue;
            AbstractOSSPropertis abstractOSSPropertis = entry.getValue();
            Class<? extends OssApiService> impl = abstractOSSPropertis.getImpl();
            try {
                sigleOssApiServiceClazzRegistryDependsOn(this.registry,impl);
            } catch (Exception e) {
                log.error("OSS spring boot start registry OSS Class Name:"+impl.getName()+"Filed "+abstractOSSPropertis.getClass().getName()+" is null");
                e.printStackTrace();
            }
        }


       /* ServiceLoader<OssApiService> load = ServiceLoader.load(OssApiService.class);
        for (OssApiService ossApiService : load) {
            sigleOssApiServiceClazzRegistry(this.registry,ossApiService.getClass());
        }*/
//        sigleOssApiServiceClazzRegistryDependsOn(this.registry,QiNiuOssApiServiceImpl.class);

       /* // spring spi加载
       List<OssApiService> ossApiServices = SpringFactoriesLoader.loadFactories(OssApiService.class, this.getClass().getClassLoader());
        for (OssApiService ossApiService : ossApiServices) {
            sigleOssApiServiceClazzRegistry(this.registry,ossApiService.getClass());
        }*/

        // 2.方式2 根据配置文件查找
       /* Map<String, AbstractOSSPropertis> beansOfType = beanFactory.getBeansOfType(AbstractOSSPropertis.class);
        if (null ==beansOfType || beansOfType.keySet().isEmpty())return;
        Set<Map.Entry<String, AbstractOSSPropertis>> entries = beansOfType.entrySet();
        for (Map.Entry<String, AbstractOSSPropertis> entry : entries) {
            AbstractOSSPropertis abstractOSSPropertis = entry.getValue();
            Class<? extends OssApiService> impl = abstractOSSPropertis.getImpl();
            sigleOssApiServiceClazzRegistry(this.registry,impl);
        }*/
    }
}
